/*
    This file is part of p4f
    Copyright (C) 2009  Sebastian Fernandez

    p4f is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as 
    published by the Free Software Foundation, either version 3 of the 
    License, or (at your option) any later version.

    p4f is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
*/

#include <socket.h>
#include <string.h>
#include <string>
#include <iostream>

Net::Net(){
#ifdef _WIN32
	wVersionRequested = MAKEWORD(2,2); // WinSock 2.2
	wsaData;
	if(WSAStartup(wVersionRequested, &wsaData)){
		WSACleanup();
		init=true; //Return error
	}
	init=false;
#else //We dont need any networking initialization
	init=true;
#endif
}

Net::~Net(){
#ifdef _WIN32
	WSACleanup();
#endif
}

//Socket native interface
Socket::Socket()
{
	memset ( &saddr, 0, sizeof(saddr) );
}

Socket::Socket(Socket &s)
{
	int addr_length = sizeof(saddr);
	
	//Copy important structures to be used by the new socket
	ssock = s.ssock;
	memcpy(&saddr, &s.saddr, sizeof(saddr));
	
}

Socket::Socket(SOCKET s)
{
	ssock = s;
}

Socket::Socket( int domain,
				int type,
				int protocol)
{
	memset ( &saddr, 0, sizeof(saddr) );
	ssock = socket(domain, type, protocol);
	return;	
}

Socket::~Socket()
{
	closesocket(ssock);
}

//Portable way to do this, mingw doesnt accept other ways
struct in_addr Socket::addr_from_host(const char *host)
{
	//not working on mingw
	//inet_pton ( AF_INET, host.c_str(), &saddr.sin_addr ); 
	struct addrinfo *info;
	struct in_addr addr;
	if(getaddrinfo(host ,0,0,&info) != 0){
		error = "error on getaddrinfo";
		freeaddrinfo(info);
		addr.s_addr = -1;
		return addr;
	}
	addr = ((struct sockaddr_in *)info->ai_addr)->sin_addr;
	//free the allocated addrinfo
	freeaddrinfo(info);
	return addr;
}

bool Socket::bind( int port )
{
	int h;
	saddr.sin_family      = PF_INET;
	saddr.sin_port        = htons( port );
	saddr.sin_addr.s_addr = htonl( INADDR_ANY );
	h = ::bind( ssock, (struct sockaddr*)&saddr, sizeof(struct sockaddr));
	if(h==-1)
		return false;
	else
		return true;
}

bool Socket::connect(std::string host, int port)
{
	int status;
	if(ssock==-1){
		error="error on socket";
		return false;
	}
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons ( port );
	saddr.sin_addr = addr_from_host(host.c_str());
	
	status = ::connect ( ssock, ( sockaddr * ) &saddr, sizeof ( saddr ) );

	if ( status == 0 ){
		return true;
	}
	else {
		error = "error on connect";
		return false;
	}
}

bool Socket::listen()
{
	int status;
	
	if(ssock==-1){
		error="error on socket";
		return false;
	}
	
	status = ::listen ( ssock, max_connections );
	if(status == -1) {
		error = "error on listen";
		return false;
	}
	else {
		return true;
	}
}

bool Socket::accept(Socket &new_socket)
{
	int addr_length = sizeof(saddr);
	
	//Copy important structures to be used by the new socket
	new_socket.ssock = ::accept( ssock, ( sockaddr * ) &saddr, ( socklen_t * ) &addr_length);
	memcpy(&new_socket.saddr, &saddr, sizeof(saddr));
	
	if ( new_socket.ssock <= 0 )
		return false;
	else
		return true;	
}

Socket *Socket::accept()
{
	Socket *tmp = new Socket();
	
	//Pass the new class to the other accept
	accept(*tmp);
	
	return tmp;
}

int Socket::send(std::string data)
{
	return send(data.c_str(), data.size());
}

int Socket::send(const char *data, int length)
{
	int status;
	if(ssock==-1){
		error="error on socket";
		return false;
	}
	
	status = ::send (ssock, data, length, 0);
	if(status == -1){
		error = "error on send";
		return -1;
	}
	else{
		return status;
	}
}

int Socket::recv(std::string &data, int length)
{
	char *tmp = new char[length];
	int status;
	
	status = ::recv ( ssock, tmp, length, 0 );
	
	data = tmp;
	delete tmp;
	return status;
}

int Socket::recv(char *data, int length)
{
	int status;
	if(ssock==-1){
		error="error on socket";
		return -1;
	}
	
	memset(data, 0, length);

	status = ::recv ( ssock, data, length, 0 );

	if ( status == -1 ){
		error = "error on receive";
		return -1;
	}
	else if ( status == 0 ){
      return 0;
	}
	else{
		return status;
	}
}

int Socket::sendto(std::string host, int port, std::string data)
{
	return sendto(host.c_str(), port, data.c_str(), data.size());
}

int Socket::sendto(const char *host, int port, const char *data, int length)
{
	int status;

	socklen_t slen;
	
	slen = sizeof(soaddr);
	if(ssock==-1){
		error="error on socket";
		return -1;
	}
	saddr.sin_family = AF_INET;
    saddr.sin_port = htons(port);
    
    saddr.sin_addr = addr_from_host(host);
    
	if (status = ::sendto(ssock, data, length, 0,
						(sockaddr *) &saddr, slen) == -1){
		error = "error on sendto";
		return -1;
	}
	return status;
}

int Socket::recvfrom(std::string &data, int length)
{
	char *tmp = new char[length];
	int status;
	
	status = recvfrom(tmp, length);
	
	data = tmp;
	delete tmp;
	return status;
}

int Socket::recvfrom(char *data, int length)
{
	int status;
	socklen_t slen;
	slen = sizeof(soaddr);
	if(ssock==-1){
		error="error on socket";
		return -1;
	}
	if (status = ::recvfrom(ssock, data, length, 0, 
					(sockaddr *)&soaddr, &slen)==-1)
	{
		error = "error on recvfrom";
		return -1;
	}
	return status;
}

//TcpSocket interfase
TcpSocket::TcpSocket(): 
	Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
{
	
}

bool TcpSocket::listen(int port)
{
	if(	Socket::bind(port) == false )
		return false;
	return Socket::listen()==false ? false : true;
}

//UdpSocket interfase
UdpSocket::UdpSocket():
	Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
{
	
}

bool UdpSocket::listen(int port)
{
	return Socket::bind(port)==false ? false : true;
}

int UdpSocket::recv(char *data, int length)
{
	return Socket::recvfrom(data, length);
}

int UdpSocket::recv(std::string &data, int length)
{
	return Socket::recvfrom(data, length);
}

bool UdpSocket::setPeer(std::string host, int port)
{
	struct addrinfo *info;
	
	saddr.sin_family = AF_INET;
    saddr.sin_port = htons(port);
    saddr.sin_addr = addr_from_host(host.c_str());
}

int UdpSocket::send(std::string data)
{
	return send(data.c_str(), data.size());
}

int UdpSocket::send(const char *data, int length)
{
	int status;

	if (status = ::sendto(ssock, data, length, 0,
						(sockaddr *) &saddr, sizeof(saddr)) == -1){
		error = "error on sendto";
		return -1;
	}
	return status;
}

const char *UdpSocket::get_remote_host()
{
	return remote_addr.c_str();
}

int UdpSocket::get_remote_port()
{
	return remote_port;
}
